package com.sunday.gateway.security.authentication;

import com.sunday.gateway.security.permissions.PermissionsManager;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.AuthenticationTrustResolver;
import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
import org.springframework.security.authorization.AuthenticatedReactiveAuthorizationManager;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import org.springframework.security.web.server.authorization.AuthorizationContext;
import reactor.core.publisher.Mono;

/**
 * 自定义权限判定
 *
 * @author sunday
 * @see ServerHttpSecurity.AuthorizeExchangeSpec.Access#authenticated()
 * @see AuthenticatedReactiveAuthorizationManager
 * @since 2024/9/26
 */
@Slf4j
public class CustomAuthenticatedReactiveAuthorizationManager implements ReactiveAuthorizationManager<AuthorizationContext> {

    private AuthenticationTrustResolver authTrustResolver = new AuthenticationTrustResolverImpl();

    CustomAuthenticatedReactiveAuthorizationManager() {
    }

    @Override
    public Mono<AuthorizationDecision> check(Mono<Authentication> authentication, AuthorizationContext authorizationContext) {
        return authentication.filter(this::isNotAnonymous)
//                .map(this::getAuthorizationDecision)
                .filter(auth -> auth.isAuthenticated())
                .cast(JwtAuthenticationToken.class)
//                .doOnNext(token -> {
//                    log.info("{}", token.getToken().getHeaders());
//                    log.info("{}", token.getTokenAttributes());
//                })
                .map(JwtAuthenticationConvert::getPermissionsSum)
                .map(permissionsSum -> {
                    String path = authorizationContext.getExchange().getRequest().getURI().getPath();
                    boolean granted = PermissionsManager.getInstances().getDecode().check(path, permissionsSum);
                    return new AuthorizationDecision(granted);
                })
                .defaultIfEmpty(new AuthorizationDecision(false));
    }

    private AuthorizationDecision getAuthorizationDecision(Authentication authentication) {
        return new AuthorizationDecision(authentication.isAuthenticated());
    }

    /**
     * Verify (via {@link AuthenticationTrustResolver}) that the given authentication is
     * not anonymous.
     *
     * @param authentication to be checked
     * @return <code>true</code> if not anonymous, otherwise <code>false</code>.
     */
    private boolean isNotAnonymous(Authentication authentication) {
        return !this.authTrustResolver.isAnonymous(authentication);
    }

    /**
     * Gets an instance of {@link AuthenticatedReactiveAuthorizationManager}
     *
     * @return
     */
    public static CustomAuthenticatedReactiveAuthorizationManager authenticated() {
        return new CustomAuthenticatedReactiveAuthorizationManager();
    }

}